﻿using DetectingKinectSencor;
using System;
using System.IO;
using System.Threading;
using Kinect.Toolbox.Record;
using Microsoft.Kinect;
using System.Windows;


namespace Kinect.Toolbox.Record
{
    /// <summary>
    /// The following class was provided by the book 
    /// Microsoft.Press.Programming.with.the.Kinect.for.Windows.Software.Development.Kit.Sep.2012
    /// </summary>
    public class PlayStreams : IDisposable
    {
        private bool DEBUGon = MainWindow.DEBUGplayStream;
        int count;

        // Attributes which are written as header of the file stream
        // This indicates to which stream (color,depth, skeleton) the frames belong
        [FlagsAttribute]
        public enum KinectRecordOptions
        {
            Color = 1,
            Depth = 2,
            Skeletons = 4
        }

        BinaryReader reader;


        Stream stream;
        readonly SynchronizationContext synchronizationContext;

        // Color event handler
        public event EventHandler<PlayColorStreamEventArgs> ColorImageFrameReady;

        // Depth event handler
        public event EventHandler<PlayDepthStreamEventArgs> DepthImageFrameReady;

        //Skeleton event handler
        public event EventHandler<PlaySkeletonStreamEventArgs> SkeletonFrameReady;


        // Definition of the colorReplay object, the type defines which method CreateFromReader() 
        // will be executed, when the method addFrame(Reader) is invoked in this class 
        public PlayerSystem<PlayColorStream> colorReplay;

        // Definition of the depthReplay object, the type defines which method CreateFromReader() 
        // will be executed, when the method addFrame(Reader) is invoked in this class 
        PlayerSystem<PlayDepthStream> depthReplay;

        //Definition of the skeletonReplay object, the type defines which method CreateFromReader() 
        // will be executed, when the method addFrame(Reader) is invoked in this class 
        public PlayerSystem<PlaySkeletonStream> skeletonReplay;

        private MainWindow mainWindow;


        /// <summary>
        /// Getter setter methode
        /// </summary>
        public bool Started { get; internal set; }

        /// <summary>
        /// Getter setter methode
        /// </summary>
        public bool IsFinished
        {
            get
            {
                if (colorReplay != null && !colorReplay.IsFinished)
                    return false;
                if (depthReplay != null && !depthReplay.IsFinished)
                    return false;
                if (skeletonReplay != null && !skeletonReplay.IsFinished)
                    return false;
                return true;
            }
        }

        /// <summary>
        /// Class which handels the color stream events
        /// </summary>
        public class PlayColorStreamEventArgs : EventArgs
        {
            public PlayColorStream ColorImageFrame { get; set; }
        }

        /// <summary>
        /// Class which handels the depth stream events
        /// </summary>
        public class PlayDepthStreamEventArgs : EventArgs
        {
            public PlayDepthStream DepthImageFrame { get; set; }
        }

        /// <summary>
        /// Class which handels the skeleton stream events
        /// </summary>
        public class PlaySkeletonStreamEventArgs : EventArgs
        {
            public PlaySkeletonStream SkeletonFrame { get; set; }
        }

        /// <summary>
        /// Constructor for class PlayStreams
        /// </summary>
        /// <param name="stream"></param>
        public PlayStreams(Stream stream, MainWindow mainWindow)
        {
            // Save the transfere paramteter stream into the local variable stream
            this.stream = stream;

            this.mainWindow = mainWindow;

            // Create a binary reader
            reader = new BinaryReader(stream);

            // Create a synchronisation context, this is necessary to raise events in the correct context
            synchronizationContext = SynchronizationContext.Current;

//-->> Here the first int "1" is read to create the color object
            // Read the header file of the stream, this defines the options (either color, depth or skeleton) stream
            KinectRecordOptions options = (KinectRecordOptions)reader.ReadInt32();

            if (DEBUGon)
                Console.WriteLine("PlayStreams Options record: " + options);

            // Create a colorReplay object
            // & --> computes the logical bitwise AND of its operands
            if ((options & KinectRecordOptions.Color) != 0)
            {
                colorReplay = new PlayerSystem<PlayColorStream>();

                // If it is a color option byte than read the next byte,
                // if not than check this byte for depth option (next if statement below)

                // Read the header file of the stream, this defines the options (either color, depth or skeleton) stream
                options = (KinectRecordOptions)reader.ReadInt32();
            }



            if (DEBUGon)
                Console.WriteLine("PlayStreams Options color: " + options);

            // Create a depthReplay object
            // & --> computes the logical bitwise AND of its operands
            if ((options & KinectRecordOptions.Depth) != 0)
            {
                depthReplay = new PlayerSystem<PlayDepthStream>();

                // If it is a depth option byte than read the next byte,
                // if not than check this byte for skeleton option (next if statement below)

                // Read the header file of the stream, this defines the options (either color, depth or skeleton) stream
                options = (KinectRecordOptions)reader.ReadInt32();
            }



            if (DEBUGon)
                Console.WriteLine("PlayStreams Options skeleton: " + options);

            // Create a skeletonReplay object
            // & --> computes the logical bitwise AND of its operands
            if ((options & KinectRecordOptions.Skeletons) != 0)
            {
                skeletonReplay = new PlayerSystem<PlaySkeletonStream>();
            }

            count = 0;

            // loop thourgh the stream, and add the frames to the right playerSystem (color,depth or skeleton)
            // based on the header which indicates each following frame
            while (reader.BaseStream.Position != reader.BaseStream.Length)
            {
                try
                {
                    // Get the header from the stream
                    KinectRecordOptions header = (KinectRecordOptions)reader.ReadInt32();
                    count++;

                    // To provide feedback how many frames are already read
                    if(count%1000==0){
                        Console.WriteLine("count " + count);
                    }

                    switch (header)
                    {
                        case KinectRecordOptions.Color:
                            // Add the frame to the color stream
                            colorReplay.AddFrame(reader);
                            break;
                        case KinectRecordOptions.Depth:
                            // Add the frame to the depth stream
                            depthReplay.AddFrame(reader);
                            break;
                        case KinectRecordOptions.Skeletons:
                            // Add the frame to the skeleton stream
                            skeletonReplay.AddFrame(reader);
                            break;
                    }

                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
        }




        /// <summary>
        /// Start the replay, the frames in each list will be shown
        /// </summary>
        public void Start()
        {
            // Check if the replay not already has been started
            if (Started)
                throw new Exception("KinectReplay already started");

            Started = true;


            if (colorReplay != null)
            {
                colorReplay.Start();
                colorReplay.FrameReady += frame => synchronizationContext.Send(state =>
                {
                    if (ColorImageFrameReady != null)
                    {
                        ColorImageFrameReady(this, new PlayColorStreamEventArgs { ColorImageFrame = frame });
                        
                        if(mainWindow.takeSnapshotOn)
                            mainWindow.takeSnapshot(frame.frameNumber);
                    }
                }, null);
            }

     
            if (depthReplay != null)
            {
                depthReplay.Start();
                depthReplay.FrameReady += frame => synchronizationContext.Send(state =>
                {
                    if (DepthImageFrameReady != null)
                    {
                        DepthImageFrameReady(this, new PlayDepthStreamEventArgs { DepthImageFrame = frame });
                    }
                }, null);
            }


          if (skeletonReplay != null)
            {
                skeletonReplay.Start();
                skeletonReplay.FrameReady += frame => synchronizationContext.Send(state =>
                {
                    if (SkeletonFrameReady != null)
                    {
                        SkeletonFrameReady(this, new PlaySkeletonStreamEventArgs { SkeletonFrame = frame });
                    }
                }, null);
           }
 

        }
        public void Stop()
        {
            if (colorReplay != null)
            {
                colorReplay.Stop();
            }
            if (depthReplay != null)
            {
                depthReplay.Stop();
            }
            if (skeletonReplay != null)
            {
                skeletonReplay.Stop();
            }
            Started = false;
        }
        public void Dispose()
        {
            Stop();
            colorReplay = null;
            depthReplay = null;
            skeletonReplay = null;
            if (reader != null)
            {
                reader.Dispose();
                reader = null;
            }
            if (stream != null)
            {
                stream.Dispose();
                stream = null;
            }
        }

        
    }
}